8. Задание по продуктовой аналитике¶
Найти точку роста бизнеса и сформулировать гипотезу улучшение бизнес процесса для роста метрик и опишите их механику тестирования с учетом того что тест не должен занимать больше 2-х недель.
- 8.1. Посчитать юнит-экономику по продуктам.
- 8.2. Из юнит-экономики определить точки роста бизнеса.
- 8.3. Понять дерево метрик для бизнеса.
- 8.4. Понять на какую метрику продукта они будут воздействовать и сформировать гипотезы.
- 8.5. Описать метод проверки гипотез с формулированием условия проведения гипотезы
# load modules and functions
import os
import numpy as np
import pandas as pd
from pandas import DataFrame
import matplotlib.pyplot as plt
import seaborn as sns
import plotly.express as px
import plotly.graph_objects as go
from IPython.display import Image
from colorama import Fore, Back, Style
import My_Function_050824_M_Filimonov as mvf # loading functions written by me for the Project from a file
import importlib
importlib.reload(mvf) # Перезагружаем модуль после изменений
<module 'My_Function_050824_M_Filimonov' from 'C:\\Users\\fmikh\\Documents\\ICH\\FIN_PRJ\\ICH_DataAnalysisProject_Python_PowerBI\\My_Function_050824_M_Filimonov.py'>
# load DataFrames:
#calls = pd.read_pickle("01_calls.pkl")
contacts = pd.read_pickle("02_contacts.pkl")
spend = pd.read_pickle("03_spend.pkl")
deals = pd.read_pickle("04_deals.pkl")
#deals.select_dtypes(include=["number"])
8.1. Расчет юнит-экономики по продуктам¶
Определение количества лидов(UA), маркетингового бюджета (AC) и стоимости привлечения лида (LTC)¶
# сравниваем число лидов в Deals и Contacts и берем там где больше
# логика:
# - пользователь не определился, его контакты занесены в Contacts, но сделка не открыта
# - контакты пользователя занесены в Contacts, но менеджер не отреагировал
# - сделка автоматически создалась при регистрации, но по каким-то причинам данные не занесены в Contacts
deal_lids = deals.Contact_Name.nunique()
print(f"Количество лидов в таблице Deals: {deal_lids}")
contacts_lids = contacts.Id.nunique()
print(f"Количество лидов в таблице Contacts: {contacts_lids}")
if deal_lids >= contacts_lids:
UA_CONST = deal_lids
else:
UA_CONST = contacts_lids
print(Fore.GREEN+f"Решение по количеству лидов: {UA_CONST}"+Style.RESET_ALL)
AC_CONST = spend['Spend'].sum()
print(Fore.RED+f"Маркетинговый бюджет: {AC_CONST:.2f}€"+Style.RESET_ALL)
LTC_CONST = AC_CONST / UA_CONST
print(Fore.BLUE+f"Стоимость привлечения потенциального клиента: {LTC_CONST:.2f}€"+Style.RESET_ALL)
Количество лидов в таблице Deals: 18089 Количество лидов в таблице Contacts: 18548 Решение по количеству лидов: 18548 Маркетинговый бюджет: 149523.45€ Стоимость привлечения потенциального клиента: 8.06€
Рассчет метрик юнит-экономики про продуктам (предлагаемый курс обучения)¶
######################################### calculate metrics by Product only
product_stats_p = deals[deals.Product != "unknown"].groupby(["Product"], observed=True).agg(
B=("Stage", lambda x: ((x == "Payment Done") & (deals.loc[x.index, "T"] > 0)).sum()), # "Stage"= "Payment Done" & T > 0
T=("T", lambda x: x[deals["Stage"] == "Payment Done"].sum()), # Payments count (Transactions)
AOV=("AOV", lambda x: x[deals["Stage"] == "Payment Done"].mean() if x[deals["Stage"] == "Payment Done"].sum() > 0 else 0),
Revenue=("Paid", lambda x: x[deals["Stage"] == "Payment Done"].sum())
).reset_index()
product_stats_p["UA"] = UA_CONST # Scaling unit flow
product_stats_p["AC"] = AC_CONST # Acquisition Cost
product_stats_p['LTC'] = LTC_CONST # LifeTime Cost
product_stats_p["C1"] = product_stats_p["B"] / product_stats_p["UA"] * 100 # Conversion rate to the first sale
product_stats_p['CLTC'] = product_stats_p['LTC'] / product_stats_p['C1'] # Customer Lifetime cost
product_stats_p['APC'] = product_stats_p['T'] / product_stats_p['B'] # Average Payment Count
product_stats_p['CLTV'] = product_stats_p['AOV'] * product_stats_p['APC'] # Customer LifeTime Value
product_stats_p['LTV'] = product_stats_p['CLTV'] * product_stats_p['C1'] # LifeTime Value
product_stats_p['CM'] = (product_stats_p['CLTV'] - product_stats_p['CLTC']) * product_stats_p['B'] # Contribution margin
# fillna NaN and inf
columns_to_fix = ["AOV","CLTC", "APC", "CLTV", "LTV", "CM"]
product_stats_p[columns_to_fix] = product_stats_p[columns_to_fix].fillna(0).replace([float('inf'), float('-inf')], 0)
# Set float display format
pd.set_option('display.float_format', '{:,.2f}'.format)
# filter produkts with T > 0 (Payment > 0, but Month of Study ==0))
product_stats_p = product_stats_p[product_stats_p["T"] > 0]
#display(product_stats_p)
# Add Total row, sum only number columns
total_p = product_stats_p.select_dtypes(include=['number']).sum()
# calculation metrics
total_p["UA"] = UA_CONST
total_p['AC'] = AC_CONST
total_p['LTC'] = LTC_CONST
total_p['AOV'] = total_p['Revenue'] / total_p['T'] if total_p['T'] != 0 else 0
total_p['APC'] = total_p['T'] / total_p['B'] if total_p['B'] != 0 else 0
total_p['C1'] = total_p['B'] / total_p['UA'] if total_p['UA'] != 0 else 0
total_p['CLTC'] = total_p['LTC'] / total_p['C1']
total_p['CLTV'] = total_p["AOV"] * total_p['APC']
total_p['LTV'] = total_p['CLTV'] * total_p['C1']
total_p['CM'] = (total_p['CLTV'] - total_p['CLTC']) * total_p['B']
total_p["Product"] = "TOTAL:"
# concat total_p row
print(Back.YELLOW + "Unit Economics Summary Table (Product)" + Style.RESET_ALL)
product_stats2 = pd.concat([product_stats_p, total_p.to_frame().T], ignore_index=True)
display(product_stats2)
######################################### visualisation metrics by Product only
# Create a bar chart with two indicators: "B" and "UA"
fig = px.bar(
product_stats_p,
x="Product",
y=["UA","B" ],
title="Units Acquisition (UA), Buyers (B), Conversion Rate (C1) per Product (log scale)",
text_auto=True,
labels={"B": "Buyers", "UA": "Units Acquisition"},
color_discrete_map={"B": "skyblue", "UA": "lightgrey"},
barmode="group", # Placement of bars nearby
log_y=True # Logarithmic scale for Y
)
# Add a conversion rate line
fig.add_trace(
go.Scatter(
x=product_stats_p["Product"],
y=product_stats_p["C1"],
mode="lines+markers+text",
text=[f"{x:.2f}%" for x in product_stats_p["C1"]],
textposition="top center",
name="Conversion Rate (%)",
line=dict(color="red", width=2),
marker=dict(size=10),
textfont=dict(color="red", size=14)
)
)
fig.update_layout(
yaxis=dict(title="UA, B number", showgrid=False),
xaxis=dict(title="Product", tickangle=0)
)
fig.show()
Unit Economics Summary Table (Product)
| Product | B | T | AOV | Revenue | UA | AC | LTC | C1 | CLTC | APC | CLTV | LTV | CM | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | Data Analytics | 3 | 20.00 | 1,183.33 | 23,200.00 | 18548 | 149,523.45 | 8.06 | 0.02 | 498.41 | 6.67 | 7,888.89 | 127.60 | 22,171.43 |
| 1 | Digital Marketing | 471 | 2,877.00 | 939.66 | 2,463,200.91 | 18548 | 149,523.45 | 8.06 | 2.54 | 3.17 | 6.11 | 5,739.70 | 14,575.16 | 2,701,905.24 |
| 2 | UX/UI Design | 228 | 1,170.00 | 944.86 | 1,032,559.55 | 18548 | 149,523.45 | 8.06 | 1.23 | 6.56 | 5.13 | 4,848.65 | 5,960.16 | 1,103,996.13 |
| 3 | Web Developer | 137 | 505.00 | 981.79 | 436,696.67 | 18548 | 149,523.45 | 8.06 | 0.74 | 10.91 | 3.69 | 3,619.00 | 2,673.08 | 494,307.76 |
| 4 | TOTAL: | 839.00 | 4,572.00 | 865.19 | 3,955,657.13 | 18,548.00 | 149,523.45 | 8.06 | 0.05 | 178.22 | 5.45 | 4,714.73 | 213.27 | 3,806,133.68 |
Рассчет метрик юнит-экономики про продуктам (предлагаемый курс обучения) и типам обучения (утренний курс/вечерний курс)¶
############# calculate metrics by Product and Education Type
product_stats = deals[(deals.Product !="unknown")&(deals.Education_Type !="unknown")].groupby(["Product","Education_Type"], observed=True).agg(
B=("Stage", lambda x: ((x == "Payment Done") & (deals.loc[x.index, "T"] > 0)).sum()), # "Stage"= "Payment Done" & T > 0
T=("T", lambda x: x[deals["Stage"] == "Payment Done"].sum()), # Payments count (Transactions)
AOV=("AOV", lambda x: x[deals["Stage"] == "Payment Done"].mean() if x[deals["Stage"] == "Payment Done"].sum() > 0 else 0),
Revenue=("Paid", lambda x: x[deals["Stage"] == "Payment Done"].sum())
).reset_index()
product_stats["UA"] = UA_CONST # Scaling unit flow
product_stats["AC"] = AC_CONST # Acquisition Cost
product_stats['LTC'] = LTC_CONST # LifeTime Cost
product_stats["AC"] = spend['Spend'].sum() # Acquisition Cost
product_stats["C1"] = product_stats["B"] / product_stats["UA"] * 100 # Conversion rate to the first sale
product_stats['LTC'] = product_stats['AC'] / product_stats['UA'] # LifeTime Cost
product_stats['CLTC'] = product_stats['LTC'] / product_stats['C1'] # Customer Lifetime cost
product_stats['APC'] = product_stats['T'] / product_stats['B'] # Average Payment Count
product_stats['CLTV'] = product_stats['AOV'] * product_stats['APC'] # Customer LifeTime Value
product_stats['LTV'] = product_stats['CLTV'] * product_stats['C1'] # LifeTime Value
product_stats['CM'] = (product_stats['CLTV'] - product_stats['CLTC']) * product_stats['B'] # Contribution margin
# fillna NaN and inf
columns_to_fix = ["AOV","CLTC", "APC", "CLTV","LTV", "CM"]
product_stats[columns_to_fix] = product_stats[columns_to_fix].fillna(0).replace([float('inf'), float('-inf')], 0)
# filter produkts with T > 0 (Payment > 0, but Month of Study ==0))
product_stats = product_stats[product_stats["T"] > 0]
# Add total_pe row, sum only number columns
total_pe = product_stats.select_dtypes(include=['number']).sum()
# calculation metrics
total_pe["UA"] = UA_CONST
total_pe['AC'] = AC_CONST
total_pe['LTC'] = LTC_CONST
total_pe['AOV'] = total_pe['Revenue'] / total_pe['T'] if total_pe['T'] != 0 else 0
total_pe['APC'] = total_pe['T'] / total_pe['B'] if total_pe['B'] != 0 else 0
total_pe['C1'] = total_pe['B'] / total_pe['UA'] if total_pe['UA'] != 0 else 0
total_pe['CLTC'] = total_pe['LTC'] / total_pe['C1']
total_pe['CLTV'] = total_pe["AOV"] * total_pe['APC']
total_pe['LTV'] = total_pe['CLTV'] * total_pe['C1']
total_pe['CM'] = (total_pe['CLTV'] - total_pe['CLTC']) * total_pe['B']
total_pe["Product"] = "TOTAL:"
total_pe["Education_Type"] = ""
# concat df and total_pe row
product_stats_t = pd.concat([product_stats, total_pe.to_frame().T], ignore_index=True)
print(Back.YELLOW + "Unit Economics Summary Table (Product & Education Type)" + Style.RESET_ALL)
pd.set_option('display.float_format', '{:,.2f}'.format)
display(product_stats_t)
Unit Economics Summary Table (Product & Education Type)
| Product | Education_Type | B | T | AOV | Revenue | UA | AC | LTC | C1 | CLTC | APC | CLTV | LTV | CM | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | Data Analytics | Morning | 3 | 20.00 | 1,183.33 | 23,200.00 | 18548 | 149,523.45 | 8.06 | 0.02 | 498.41 | 6.67 | 7,888.89 | 127.60 | 22,171.43 |
| 1 | Digital Marketing | Evening | 113 | 764.00 | 423.49 | 288,181.82 | 18548 | 149,523.45 | 8.06 | 0.61 | 13.23 | 6.76 | 2,863.25 | 1,744.38 | 322,051.60 |
| 2 | Digital Marketing | Morning | 358 | 2,113.00 | 1,102.58 | 2,175,019.09 | 18548 | 149,523.45 | 8.06 | 1.93 | 4.18 | 5.90 | 6,507.71 | 12,560.71 | 2,328,265.51 |
| 3 | UX/UI Design | Evening | 57 | 284.00 | 462.74 | 116,798.63 | 18548 | 149,523.45 | 8.06 | 0.31 | 26.23 | 4.98 | 2,305.60 | 708.53 | 129,923.81 |
| 4 | UX/UI Design | Morning | 171 | 886.00 | 1,108.39 | 915,760.92 | 18548 | 149,523.45 | 8.06 | 0.92 | 8.74 | 5.18 | 5,742.89 | 5,294.56 | 980,539.13 |
| 5 | Web Developer | Morning | 137 | 505.00 | 981.79 | 436,696.67 | 18548 | 149,523.45 | 8.06 | 0.74 | 10.91 | 3.69 | 3,619.00 | 2,673.08 | 494,307.76 |
| 6 | TOTAL: | 839.00 | 4,572.00 | 865.19 | 3,955,657.13 | 18,548.00 | 149,523.45 | 8.06 | 0.05 | 178.22 | 5.45 | 4,714.73 | 213.27 | 3,806,133.68 |
########## Visualization by products, number of leads, customers and conversion rate
product_stats = product_stats.sort_values(by="Product")
# Create a stacked bar chart for Buyers (B) per Product, split by Morning/Evening
fig = px.bar(
product_stats,
x="Product",
y="B",
color="Education_Type",
log_y=True,
barmode="stack",
text_auto=True,
labels={"B": ""},
title="Buyers (B), Conversion Rate (C1) per Product and Education Type (log scale)",
color_discrete_map={"Evening": "#FFDDC1","Morning": "lightblue"}, # #FFDDC1, lightblue
category_orders={"Education_Type": ["Evening", "Morning"], # Обратный порядок для стэка
"Product": ["Digital Marketing","UX/UI Design","Web Developer","Data Analytics"]}
)
# Apply log scale correction
#fig.update_layout(yaxis_type="log")
# Filter data for Morning and Evening
morning_stats = product_stats[product_stats["Education_Type"] == "Morning"]
evening_stats = product_stats[product_stats["Education_Type"] == "Evening"]
# Filter Evening data, removing zero values
morning_stats_filtered = morning_stats[morning_stats["C1"] > 0]
evening_stats_filtered = evening_stats[evening_stats["C1"] > 0]
morning_stats_filtered = morning_stats_filtered.sort_values(by="B",ascending=False)
evening_stats_filtered = evening_stats_filtered.sort_values(by="B",ascending=False)
# Add a conversion rate line for Morning
fig.add_trace(
go.Scatter(
x=morning_stats_filtered["Product"],
y=morning_stats_filtered["C1"],
mode="lines+markers+text",
text=[f"{x:.2f}%" for x in morning_stats_filtered["C1"]],
textposition="bottom center",
name="Conversion Rate - Morning",
line=dict(color="blue", width=2),
marker=dict(size=6, color="blue"),
textfont=dict(color="blue", size=12),
hovertemplate="Product: %{x}<br>Education Type: Morning<br>Conversion Rate: %{y}%<extra></extra>"
)
)
# Add a conversion rate line for Evening (only non-zero values)
fig.add_trace(
go.Scatter(
x=evening_stats_filtered["Product"],
y=evening_stats_filtered["C1"],
mode="lines+markers+text",
text=[f"{x:.2f}%" for x in evening_stats_filtered["C1"]],
textposition="bottom center",
name="Conversion Rate - Evening",
line=dict(color="orange", width=2),
marker=dict(size=6, color="orange"),
textfont=dict(color="orange", size=12),
hovertemplate="Product: %{x}<br>Education Type: Evening<br>Conversion Rate: %{y}%<extra></extra>"
)
)
fig.update_layout(
legend=dict(x=1.001, y=1, traceorder="normal"), # reversed
yaxis=dict(title="Bayers number", showgrid=False),
xaxis=dict(title="Product", tickangle=0)
)
fig.show()
Визуализируем маржинальную прибыль по продуктам¶
product_stats = product_stats.sort_values(by="Product")
# Create a stacked bar chart for Buyers (B) per Product, split by Morning/Evening
fig = px.bar(
product_stats,
x="Product",
y="CM",
color="Education_Type",
barmode="stack",
text_auto=True,
labels={"CМ": "Contribution Margin, €"},
title="Contribution Margin per Product and Education Type (log scale)",
color_discrete_map={"Morning": "lightblue", "Evening": "#FFDDC1"}, # #FFDDC1, lightblue
category_orders={"Education_Type": ["Evening", "Morning"], # Обратный порядок для стэка
"Product": ["Digital Marketing","UX/UI Design","Web Developer","Data Analytics"]}
)
fig.update_layout(
legend=dict(x=1.001, y=1, traceorder="normal"), # reversed
yaxis=dict(title="Contribution Margin, €", showgrid=False),
xaxis=dict(title="Product", tickangle=0)
)
fig.show()
Анализ юнит-экономики образовательных продуктов¶
Ключевые метрики: | Метрика | Значение | |---------|---------| | Число клиентов (B) | 839 | | Число платежей (T) | 4,572 | | Средний чек (AOV) | 865.19 € | | Общий оборот | 3,955,657.13 € | | Средняя конверсия (C1) | 0.05 (5%) | | Маржинальная прибыль (CM) | 3,806,133.68 € |
ТОП-3 самых прибыльных продуктов: | Продукт | Формат | Маржинальная прибыль (CM, €) | |---------|--------|------------------------------| | Digital Marketing | Morning | 2,306,959.13 | | UX/UI Design | Morning | 980,539.13 | | Web Developer | Morning | 494,307.76 |
Выводы:
- Утренний Digital Marketing приносит наибольшую прибыль с высоким средним чеком.
- UX/UI Design (Morning) и Web Developer (Morning) также высокодоходные, стоит увеличить рекламный бюджет.
- Наименее прибыльные направления: | Продукт | Формат | Маржинальная прибыль (CM, €) | |---------|--------|------------------------------| | Data Analytics | Morning | 22,171.43 | | UX/UI Design | Evening | 129,923.81 |
Выводы:
- Data Analytics (Morning) показывает низкую прибыльность (CM = 22,171.43 €) и слабую конверсию (C1 = 0.02).
- UX/UI Design (Evening) имеет низкую маржинальную прибыль, требует пересмотра маркетинговой стратегии.
- Анализ стоимости привлечения клиентов (CLTC) | Продукт | Формат | CLTC (€) | |---------|--------|---------| | Data Analytics | Morning | 498.41 | | UX/UI Design | Evening | 26.23 | | Digital Marketing | Morning | 4.20 |
Выводы:
- Data Analytics (Morning) — высокая стоимость привлечения (CLTC = 498.41 €) при слабой конверсии.
- Digital Marketing (Morning) — низкая стоимость привлечения (CLTC = 4.20 €), лучший канал для роста.
- Рекомендации:
- Фокус на утренние курсы Digital Marketing, UX/UI Design, Web Developer, они самые прибыльные.
- Пересмотреть стратегию Data Analytics (Morning), низкая конверсия + высокая стоимость привлечения.
- Оптимизировать UX/UI Design (Evening), низкая прибыльность требует переработки продукта или маркетинга.
- Увеличить маркетинговый бюджет на Digital Marketing (Morning), лучший канал для роста доходов.
8.2. Определение точек роста бизнеса¶
Следуя теории юнит-экономики обозначим шаги для определения точек (драйверов) роста бизнеса:
- Используя Теорию ограничений Голдратта найдем "бутылочное горлышко", а именно, улучшим поочередно метрики UA, LTC, AOV, C1, CLTC на 10% ;
- Определим, какие из метрик оказывают наибольшее влияние на CM, при наименьших затратах;
- Найдем оптимальную конфигурацию метрик с учетом ограничений - затрат на улучшение метрики.
- Генерируем гипотезы, связанные только с этой метрикой и строим HADI циклы для проверки гипотез.
- Рассчитываем параметры тестов.
- Моделируем проедение А/В тестирование.
def simulate_metric_changes(df, metrics_to_test, change_percent=10):
"""
Modifies each metric separately and analyzes its impact on CM.
:param df: DataFrame with economic indicators.
:param metrics_to_test: Dictionary of metrics {metric name: increase (True) or decrease (False)}.
:param change_percent: Percentage change for each metric (default is 10%).
:return: DataFrame with CM changes, ranked by decreasing CM Change (%).
"""
results = []
for metric, increase in metrics_to_test.items():
modified_df = df.copy()
# Modify ONLY the current metric
if metric in modified_df.columns:
factor = 1 + (change_percent / 100) if increase else 1 - (change_percent / 100)
modified_df[metric] *= factor
# Recalculate dependent metrics
modified_df['CLTC'] = modified_df['LTC'] / modified_df['C1'] # Customer Lifetime Cost
modified_df['APC'] = modified_df['T'] / modified_df['B'] # Average Payment Count
modified_df['CLTV'] = modified_df['AOV'] * modified_df['APC'] # Customer Lifetime Value
modified_df['LTV'] = modified_df['CLTV'] * modified_df['C1'] # Lifetime Value
modified_df['CM'] = (modified_df['CLTV'] - modified_df['CLTC']) * modified_df['B'] # Contribution Margin
# Calculate CM change
modified_df["CM_Change"] = (modified_df["CM"] - df["CM"]) / df["CM"] * 100 # % CM change
# Store results for the current metric
modified_df["Metric Tested"] = metric
results.append(modified_df[["Product", "Education_Type", "Metric Tested", "CM", "CM_Change"]])
# Combine all results and rank by decreasing CM Change (%)
final_df = pd.concat(results).sort_values(by="CM_Change", ascending=False)
# Select only numerical columns
numeric_cols = final_df.select_dtypes(include=["number"]).columns
# Replace `NaN` and `inf` only in numerical columns
final_df[numeric_cols] = final_df[numeric_cols].fillna(0).replace([np.inf, -np.inf], 0)
return final_df
########################## Test data
metrics_to_test = {"UA":True,"LTC":False,"AOV": True, "C1": True, "CLTC": False} # True - increase, False - decrease metric value
change_percent = 10 # 10% change
########################## Run function
modified_product_stats = simulate_metric_changes(product_stats, metrics_to_test, change_percent)
########################## Display all results
modified_product_stats = modified_product_stats.sort_values(by=["Product", "Education_Type", "Metric Tested"])
print(Back.YELLOW +f"\nImpact on Contribution Margin (CM) of {change_percent}% improvement in metrics: {list(metrics_to_test.keys())}"+Style.RESET_ALL)
print(modified_product_stats)
########################## Display Zero filtered results
modified_product_stats = modified_product_stats[modified_product_stats.CM_Change > 0].sort_values(by="CM_Change", ascending=False)
print(Back.YELLOW +f"\nImpact on Contribution Margin (CM) of {change_percent}% improvement in metrics: {list(metrics_to_test.keys())}"+Style.RESET_ALL)
print("(Zero values filtered, sorted by improvement percentage)")
print(modified_product_stats)
Impact on Contribution Margin (CM) of 10% improvement in metrics: ['UA', 'LTC', 'AOV', 'C1', 'CLTC'] Product Education_Type Metric Tested CM CM_Change 1 Data Analytics Morning AOV 24,538.10 10.67 1 Data Analytics Morning C1 22,307.36 0.61 1 Data Analytics Morning CLTC 22,171.43 0.00 1 Data Analytics Morning LTC 22,320.96 0.67 1 Data Analytics Morning UA 22,171.43 0.00 2 Digital Marketing Evening AOV 354,406.28 10.05 2 Digital Marketing Evening C1 322,187.53 0.04 2 Digital Marketing Evening CLTC 322,051.60 0.00 2 Digital Marketing Evening LTC 322,201.12 0.05 2 Digital Marketing Evening UA 322,051.60 0.00 3 Digital Marketing Morning AOV 2,561,241.59 10.01 3 Digital Marketing Morning C1 2,328,401.44 0.01 3 Digital Marketing Morning CLTC 2,328,265.51 0.00 3 Digital Marketing Morning LTC 2,328,415.04 0.01 3 Digital Marketing Morning UA 2,328,265.51 0.00 6 UX/UI Design Evening AOV 143,065.71 10.12 6 UX/UI Design Evening C1 130,059.74 0.10 6 UX/UI Design Evening CLTC 129,923.81 0.00 6 UX/UI Design Evening LTC 130,073.33 0.12 6 UX/UI Design Evening UA 129,923.81 0.00 7 UX/UI Design Morning AOV 1,078,742.57 10.02 7 UX/UI Design Morning C1 980,675.06 0.01 7 UX/UI Design Morning CLTC 980,539.13 0.00 7 UX/UI Design Morning LTC 980,688.66 0.02 7 UX/UI Design Morning UA 980,539.13 0.00 9 Web Developer Morning AOV 543,888.06 10.03 9 Web Developer Morning C1 494,443.69 0.03 9 Web Developer Morning CLTC 494,307.76 0.00 9 Web Developer Morning LTC 494,457.28 0.03 9 Web Developer Morning UA 494,307.76 0.00 Impact on Contribution Margin (CM) of 10% improvement in metrics: ['UA', 'LTC', 'AOV', 'C1', 'CLTC'] (Zero values filtered, sorted by improvement percentage) Product Education_Type Metric Tested CM CM_Change 1 Data Analytics Morning AOV 24,538.10 10.67 6 UX/UI Design Evening AOV 143,065.71 10.12 2 Digital Marketing Evening AOV 354,406.28 10.05 9 Web Developer Morning AOV 543,888.06 10.03 7 UX/UI Design Morning AOV 1,078,742.57 10.02 3 Digital Marketing Morning AOV 2,561,241.59 10.01 1 Data Analytics Morning LTC 22,320.96 0.67 1 Data Analytics Morning C1 22,307.36 0.61 6 UX/UI Design Evening LTC 130,073.33 0.12 6 UX/UI Design Evening C1 130,059.74 0.10 2 Digital Marketing Evening LTC 322,201.12 0.05 2 Digital Marketing Evening C1 322,187.53 0.04 9 Web Developer Morning LTC 494,457.28 0.03 9 Web Developer Morning C1 494,443.69 0.03 7 UX/UI Design Morning LTC 980,688.66 0.02 7 UX/UI Design Morning C1 980,675.06 0.01 3 Digital Marketing Morning LTC 2,328,415.04 0.01 3 Digital Marketing Morning C1 2,328,401.44 0.01
Вывод:
- Наилучшим драйвером роста маржинальной прибыли (CM) при улучшении одного показателя из 5 тестируемых является увеличение AOV - среднего чека;
- Вторым в рейтинге драйвером роста CM является уменьшение LTC - затраты на юнита масштабирования, или стоимость привлечения потенциального клиента
- Третим в рейтинге драйвером роста CM является увеличение C1 - конверсия в первую сделку
8.3. Дерево метрик для бизнеса¶
Image("Metrics_Tree.png")
8.4. Формулирование гипотез для метрик продукта¶
Impact on Contribution Margin (CM) of 10% improvement in metrics: ['UA', 'LTC', 'AOV', 'C1', 'CLTC']
(Zero values filtered, sorted by improvement percentage)
Product Education_Type Metric Tested CM CM_Change
1 Data Analytics Morning AOV 24,538.10 10.67
6 UX/UI Design Evening AOV 143,065.71 10.12
2 Digital Marketing Evening AOV 373,884.40 10.04
9 Web Developer Morning AOV 543,888.06 10.03
7 UX/UI Design Morning AOV 1,078,742.57 10.02
3 Digital Marketing Morning AOV 2,537,804.56 10.01
1 Data Analytics Morning LTC 22,320.96 0.67
1 Data Analytics Morning C1 22,307.36 0.61
6 UX/UI Design Evening LTC 130,073.33 0.12
6 UX/UI Design Evening C1 130,059.74 0.10
2 Digital Marketing Evening LTC 339,908.50 0.04
2 Digital Marketing Evening C1 339,894.91 0.04
9 Web Developer Morning LTC 494,457.28 0.03
9 Web Developer Morning C1 494,443.69 0.03
7 UX/UI Design Morning LTC 980,688.66 0.02
7 UX/UI Design Morning C1 980,675.06 0.01
3 Digital Marketing Morning LTC 2,307,108.65 0.01
3 Digital Marketing Morning C1 2,307,095.06 0.01
- "Бутылочное горлышко" в юнит-экономике это метрика, изменение которой на небольшую величину дает кратный рост маржинальной прибыли при наименьших затратах на изменение метрики.
- Согласно выводу из анализа результатов расчета по определению драйверов роста маржинальной прибыли - это метрики AOV LTC и С1.
- Установим, что затраты на улучшение метрики LTC выше чем AOV и C1 и исключим ее из дальнейшего анализа (в реальности данные по затратам на улучшение метрик определяютя руководством на основе анализа затрат).
- В учебных целях для для формулировки и тестирования гипотез выберем два продукта: лидера Digital Marketing (Morning) и аусайдера Data Analytics (Morning) продаж. Тем более, что необходимость их улучшения прямо связана с выводами и рекомендациями, которые были сделаны по результатам проведенного анализа юнит-экономики образовательных продуктов:
- Оптимизировать Data Analytics (Morning) → доходность (средний чек) и маркетинг (снизить затраты на привлечение, повысить конверсию). По степени затрат эти направления соизмеримы, но бутылочным горлышком здесь есть метрика cреднего чека AOV, которая вносит наибольший среди всех продуктов вклад (10.67%) в маржинальную прибыль. Для этого продукта увеличить AOV (1183,33€) можно, например за счет предложения при покупке курса дополнительных продуктов (продвинутые учебные материалы, книги, сертификаты на онлайн тренинги по углубленному курсу дата-анализа и .т.д.)
Формулировка гипотезы №1 (Продукт: Data Analytics (Morning)) по S.M.A.R.T:
- Предложения дополнительных продуктов при покупке курса (продвинутые учебные материалы, книги, сертификаты на онлайн тренинги по углубленному курсу дата-анализа и .т.д.) увеличит средний чек на 10%. Доверительный интервал AOV 5%: 1301.66 ± 32.54€
- Сконцентрироваться на продажах Digital Marketing (Morning), так как он лидер по доходности → стоит делать агрессивные рекламные кампании (C1=1.92%) Здесть протестируем гипотезу увеличения конверсии, хотя ее вклад в увеличение маржинальный прибыли (0.01% ), ниже чем у AOV, однако затраты на ее увеличение ниже.
Формулировка гипотезы №2 (Продукт: Digital Marketing (Morning) по S.M.A.R.T:
- Введение реферальной программы с моделью «5% на 1 месяц обучения за приглашенного клиента» увеличит конверсию на 10%. Доверительный интервал C1 5%: 2.12 ± 0.02%
8.5. Описание метода проверки гипотез с формулированием условий проверки¶
HADI-циклы для проверки гипотез¶
Image("HADI-циклы.png")
Расчет доверительных интервалов и размера выборки для проведения А|B тестирования¶
При расчетах примем следующие допущения:
- Вероятность отклонения основной (или нулевой) гипотезы при проверке статистических гипотез в случае, когда конкурирующая (или альтернативная) гипотеза верна (confidence level) - 0.95
- Доверительный интервал - 5%
- Минимально обнаруживаемый эффект - 1%
Для этих допущений итоговое число экспериментов в каждой группе рассчитаем по формуле:
$n = \frac{16*p*(1-p))}{x^2}$,
где:
- $n$ - итоговое число экспериментов в каждой группе;
- $p$ - базовая конверсия;
- $x$ - минимально обнаруживаемый эффект в %.
##### Calculating confidence intervals and sample size for A|B testing first hypothesis
print(Back.YELLOW+"Calculation for the first hypothesis:"+Style.RESET_ALL)
print(Fore.BLUE+"Offering additional products when purchasing a course (advanced study materials, books,")
print("certificates for online trainings on an advanced data analysis course, etc.)")
print("will increase the Average Order Value by 10%. Confidence interval 5%: 1301.66 ± 32.54€" +Style.RESET_ALL)
p = 0.0002
aov = 1183.33
xi = 0.02
print(f"Base conversion rate p={p*100:.2f}%")
print(f"Minimum detectable effect xi={xi*100:.2f}%")
print(f"Base Average Order Value AOV={aov:.2f}%")
n = (16*p*(1-p))/xi**2
print(f"Required sample size for A|B testing: {int(n)} participants")
print(f"Total number of participants required for the experiment: {int(n)*2}")
aov_new = aov + aov*0.1
print(f"New AOV with confidence interval,€: {aov_new:.2f} ± {aov_new*0.025:.2f}€\n")
##### Calculating confidence intervals and sample size for A|B testing second hypothesis
print(Back.YELLOW+"Calculation for the second hypothesis:"+Style.RESET_ALL)
print(Fore.MAGENTA+'Introducing a referral program with the model "5% off 1 month of training for a referred client"')
print('will increase conversion by 10%. Confidence interval 5%: 2.12 ± 0.05%'+Style.RESET_ALL)
p = 0.0192
xi = 0.02
print(f"Base conversion rate p={p*100:.2f}%")
print(f"Minimum detectable effect xi={xi*100:.2f}%")
n = (16*p*(1-p))/xi**2
print(f"Required sample size for A|B testing: {int(n)} participants")
print(f"Total number of participants required for the experiment: {int(n)*2} participants")
p_new = p*100 + p*100*0.1
print(f"New Conversion Rate with confidence interval,%: {p_new:.2f} ± {p_new*0.025:.2f}%")
Calculation for the first hypothesis: Offering additional products when purchasing a course (advanced study materials, books, certificates for online trainings on an advanced data analysis course, etc.) will increase the Average Order Value by 10%. Confidence interval 5%: 1301.66 ± 32.54€ Base conversion rate p=0.02% Minimum detectable effect xi=2.00% Base Average Order Value AOV=1183.33% Required sample size for A|B testing: 7 participants Total number of participants required for the experiment: 14 New AOV with confidence interval,€: 1301.66 ± 32.54€ Calculation for the second hypothesis: Introducing a referral program with the model "5% off 1 month of training for a referred client" will increase conversion by 10%. Confidence interval 5%: 2.12 ± 0.05% Base conversion rate p=1.92% Minimum detectable effect xi=2.00% Required sample size for A|B testing: 753 participants Total number of participants required for the experiment: 1506 participants New Conversion Rate with confidence interval,%: 2.11 ± 0.05%
Формулирование исходные данных для экспериментов¶
Результаты расчетов позволяют сформулировать исходные данные для эксперимента:
Гипотеза №1
- Общее число участников тестирования - 14
- Среднее значение чек AOV ожидается: 1301.66 ± 32.54€
- Срок проведения тестирования 14 дней.
Гипотеза №2
- Общее число участников тестирования - 1506
- Среднее значение коэффициента конверсии С1 ожидается: 2.12 ± 0.05%
- Срок проведения тестирования 14 дней.
Проверим срок проведения тестирования¶
По условию эксперимента мы планируем тест на 14 дней (2 недели).
- для гипотезы №1 для эксперимента нам необходимо 14 участник
- для гипотезы №2 для эксперимента нам необходимо 1506 участников
Сделаем расчет среднего числа лидов для этих интервалов и построим графики по количеству сделок (UA) с течением времени
При этом сгруппируем сделки по временным периодам:
- 2 недели
- месяц
import plotly.graph_objects as go
################ Calculation
deals["Deal_Created_Date_2W"] = deals["Created_Time"].dt.to_period("2W")
deals["Deal_Created_Date_M"] = deals["Created_Time"].dt.to_period("M")
deals_trend_2W = deals.groupby("Deal_Created_Date_2W")["Id"].count().reset_index()
deals_trend_2W.rename(columns={"Id": "Deals_Created"}, inplace=True)
deals_trend_M = deals.groupby("Deal_Created_Date_M")["Id"].count().reset_index()
deals_trend_M.rename(columns={"Id": "Deals_Created"}, inplace=True)
deals_trend_2W["Deal_Created_Date"] = deals_trend_2W["Deal_Created_Date_2W"].dt.to_timestamp()
deals_trend_M["Deal_Created_Date"] = deals_trend_M["Deal_Created_Date_M"].dt.to_timestamp()
deals_trend_2W = deals_trend_2W.query("Deal_Created_Date > '2022-12-31'")
deals_trend_M = deals_trend_M.query("Deal_Created_Date > '2022-12-31'")
print(Fore.BLUE+f"Average Deals number per two weeks period: {deals_trend_2W["Deals_Created"].mean():.0f}"+Style.RESET_ALL)
print(Fore.MAGENTA+f"Average Deals number per month period:: {deals_trend_M["Deals_Created"].mean():.0f}"+Style.RESET_ALL)
################ VISUALISATION
fig = go.Figure()
fig.add_trace(
go.Scatter(
x=deals_trend_2W["Deal_Created_Date"],
y=deals_trend_2W["Deals_Created"],
mode="lines+markers+text",
text=deals_trend_2W["Deals_Created"],
textposition="top center",
textfont=dict(size=8, color="black"),
name="Deals Created (2 Weeks)",
line=dict(color="blue", width=2),
marker=dict(size=6, color="blue"),
visible=True
)
)
fig.add_trace(
go.Scatter(
x=deals_trend_M["Deal_Created_Date"],
y=deals_trend_M["Deals_Created"],
mode="lines+markers+text",
text=deals_trend_M["Deals_Created"],
textposition="top center",
textfont=dict(size=8, color="black"),
name="Deals Created (Monthly)",
line=dict(color="blue", width=2, dash="dash"),
marker=dict(size=6, color="blue"),
visible=False
)
)
# drop-down switch
fig.update_layout(
updatemenus=[
dict(
buttons=[
dict(label="2 Weeks", method="update", args=[{"visible": [True, False]}]),
dict(label="Monthly", method="update", args=[{"visible": [False, True]}]),
],
direction="down",
showactive=True,
x=0.2, y=1.1
)
],
title="Deals Trends (Selectable Periods)",
xaxis_title="Period",
yaxis_title="Deals Number",
template="plotly_white",
hovermode="x unified",
legend=dict(x=0.9, y=1.18, title="Metrics")
)
fig.show()
Average Deals number per two weeks period: 415 Average Deals number per month period:: 1799
Из расчетов и графика следует:
- среднее число сделок в месяц - 1799, в двухнедельном переиоде - 415;
- для проверки Гипотезы №1 - участников достаточно;
- для проверки Гипотезы №2 - участников сделки как в двухнедельном периоде не достаточно, однако если взять срок эксперимента 1 месяц, то участников эксперимента будет достаточно.
Вывод:
- Проведение эксперимента по проверке Гипотезы №1 в выбранный срок 2 недели - ВОЗМОЖНО;
- Проведение эксперимента по проверке Гипотезы №2 в срок 2 недели - НЕ ВОЗМОЖНО из-за недостатоной численности участников, поэтому срок эксперимента увеличим и установим один месяц
Моделирование проведения A|B теста¶
Гипотеза №1 - проверка по t-критерию Стьюдента¶
import scipy.stats as stats
"""
Hypothesis №1
Offering additional products when purchasing a course (advanced study materials, books,
certificates for online trainings on an advanced data analysis course, etc.)
will increase the Average Order Value by 10%. Confidence interval 5%: 1.301,66 ± 32.54€
Null Hypothesis №1
Offers of additional products when purchasing a course do not affect the Average Order Value.
"""
# Initial data
AOV_base = 1183.33 # Average Order Value without additional products
AOV_test = AOV_base * 1.10 # Average Order Value with a 10% increase
std_base = 29.58 # Standard deviation of AOV in the control group
std_test = 32.54 # Standard deviation of AOV in the test group
n_A = 7 # Sample size of group A (control)
n_B = 7 # Sample size of group B (test)
# Generate samples
np.random.seed(42)
sample_A = np.random.normal(AOV_base, std_base, n_A)
sample_B = np.random.normal(AOV_test, std_test, n_B)
# Perform Student's t-test
t_statistic, p_value = stats.ttest_ind(sample_A, sample_B, equal_var=False)
# Output results
print(f"T-statistic: {t_statistic:.4f}")
print(f"P-value: {p_value:.4f}")
# Interpret results
alpha = 0.05 # Significance level (5%)
if p_value < alpha:
print(Fore.GREEN +"Hypothesis NOT rejected: AOV increase by 10% is statistically significant."+Style.RESET_ALL)
else:
print(Fore.RED +"Hypothesis rejected: Difference in AOV is not statistically significant."+Style.RESET_ALL)
T-statistic: -6.7227
P-value: 0.0000
Hypothesis NOT rejected: AOV increase by 10% is statistically significant.
Интерпретация результата t-теста Стьюдента для гипотезы №1:
- T-статистика: -6.7227 — показывает степень различий между двумя выборками.
- P-value: 0.0000 — вероятность того, что наблюдаемые различия могли возникнуть случайно, если нулевая гипотеза верна.
- Уровень значимости (alpha): 0.05 — заданный порог для принятия решения.
Вывод
- p-value = 0.0000 < 0.05, что означает, что мы отвергаем нулевую гипотезу.
- Гипотеза НЕ отвергнута, значит, предложение дополнительных продуктов действительно статистически значимо увеличивает средний чек на 10%.
Вывод: Внедрение дополнительных предложений при покупке курса может существенно увеличить AOV.
- Рекомендация:
- Разработать стратегию upsell (дополнительных продуктов), чтобы максимизировать доход от каждой покупки
- Полезно оценить долгосрочное влияние на LTV (Lifetime Value), чтобы понять, насколько устойчив этот рост AOV.
Гипотеза №2 - проверка по t-критерию Стьюдента¶
import scipy.stats as stats
"""
Hypothesis №2
Introducing a referral program with the model "5% off 1 month of training for a referred client"
will increase conversion rate by 10%. Confidence interval 5%: 2.12 ± 0.05%
Null Hypothesis №2
The introduction of a referral program with the model "5% for 1 month of training
for an invited client" does not affect conversion rate
"""
# Initial data
C1_base = 1.93 # текущая конверсия
C1_test = C1_base * 1.10 # Average Order Value with a 10% increase
std_base = 0.048 # Standard deviation of AOV in the control group
std_test = 0.051 # Standard deviation of AOV in the test group
n_A = 753 # Sample size of group A (control)
n_B = 753 # Sample size of group B (test)
# Generate samples
np.random.seed(42)
sample_A = np.random.normal(C1_base, std_base, n_A)
sample_B = np.random.normal(C1_test, std_test, n_B)
# Perform Student's t-test
t_statistic, p_value = stats.ttest_ind(sample_A, sample_B, equal_var=False)
# Output results
print(f"T-statistic: {t_statistic:.4f}")
print(f"P-value: {p_value:.4f}")
# Interpret results
alpha = 0.05 # Significance level (5%)
if p_value < alpha:
print(Fore.GREEN +"Hypothesis NOT rejected: C1 increase by 10% is statistically significant."+Style.RESET_ALL)
else:
print(Fore.RED +"Hypothesis rejected: Difference in C1 is not statistically significant."+Style.RESET_ALL)
T-statistic: -79.4407
P-value: 0.0000
Hypothesis NOT rejected: C1 increase by 10% is statistically significant.
Интерпретация результата t-теста Стьюдента для гипотезы №2:
Основные показатели
- T-статистика: -79.4407 (очень высокая абсолютная величина)
- P-значение: 0.0000 (намного ниже уровня значимости α = 0.05)
- Уровень значимости (α): 0.05
Выводы по тесту:
P-value (0.0000) < α (0.05), значит, мы НЕ отвергаем гипотезу о том, что внедрение реферальной программы увеличивает конверсию на 10%.
Высокая абсолютная величина t-статистики (-79.4407) подтверждает, что разница между контрольной и тестовой группами существенная и не случайная.
Следовательно, реферальная программа действительно оказывает положительное влияние на конверсию (C1).
Вывод для бизнеса
- Реферальная программа работает и статистически значимо увеличивает C1 на 10%.
- Можно масштабировать программу, предлагая 5% скидку за приглашенного клиента на более широкую аудиторию.
- Высокая статистическая значимость подтверждает, что этот эффект устойчив, а не случайный.
- Следующий шаг: протестировать различные размеры скидки (например, 3%, 7%) и проанализировать их влияние.
Рекомендации:
- Углубить анализ на долгосрочное влияние на LTV (Lifetime Value).
- Разработать автоматизированный процесс реферальных вознаграждений для удобства клиентов.
- Провести A/B тестирование с разными скидками и определить оптимальный размер бонуса.